Recent Changes - Search:

Home & News

Organization

Assignments

Support

edit SideBar

Lab2

General Remarks

  • We suggest to read the following tutorials before you start implementing:
  • Group work is NOT allowed in the lab. You have to work alone. Discussions with colleagues (e.g., in the forum) are allowed but the code has to be written alone.
  • Be sure to check the Tricky Parts section for questions!

Submission Guide (for all Labs)

Submission

  • You must upload your solution using the Teaching Tool before the submission deadline: 04.12., 18:00 cet. - the deadline is hard! You are responsible for submitting your solution in time. If you do not submit, you won't get any points!
  • Upload your solution as a ZIP file. Please submit only the sources including your build and properties file (see below) of your solution (not the compiled class files and no third-party libraries).
  • Your submission must compile and run in our lab environment. Use and complete the provided ant template.
  • Before the submission deadline, you can upload your solution as often as you like. Note that any existing submission will be replaced by uploading a new one.
  • Please make sure that your upload was successful (i.e., you should be able to download your solution - as the tutors will do during the interview).

Interviews

  • After the submission deadline, there will be a mandatory interview (Abgabegespräch). You must register for a time slot to the interviews using the Teaching Tool.
  • You can do the interview only if you submitted your solution before the deadline!
  • The interview will take place in the DSLab. During the interview, you will be asked about the solution that you uploaded (i.e., changes after the deadline will not be taken into account!). In the interview you need to explain your code, design and architecture in detail.
  • Remember that you can do the interview only once!

Description

In this assignment you will learn:

  • the basics of a simple distributed object technology (RMI)
  • how to bind and lookup objects with a naming service
  • how to implement callbacks with RMI

Overview

In this assignment we will develop a simplified messenger application (like ICQ or MSN Messenger), which allows users to find other users, become friends and exchange messages.

First, here’s a short explanation of the domain:

  • For each user the following master data is captured: username, password, first name, last name, birthday, profession, email address and hobbies. The username must be unique among all users.
  • Users may establish friendships. For friendships there always must be two agreeing parties, so one user has to send a friendship request to another user, who may then accept or reject it. Friendships allow users to send messages to each other; also friends of a user are notified when he/she goes online or offline or when he unregisters. Users may also stop being friends.
  • The public user data (i.e. the data that may be inspected by other users) contains the whole user master data (except the password), the usernames of all friends and the current connection state (i.e. online/offline) and the time the user was last online.

For this assignment we use a conventional client-server architecture as shown in Figure 1. There is one central server who’s responsible for managing the user information. For each operation (shown using solid lines) the clients have to contact the server, which implements the required behavior. Hence, in contrast to Lab1 there’s no direct user-to-user communication – every operation is propagated via the server.

Figure 1: Messenger Architecture

Most operations invoked on the server lead to notifications (shown using dashed lines) of other users, for instance login or send messages. These event notifications will be implemented using RMI callbacks. If the recipients of any event (e.g., friendship request/acceptation, incoming messages, etc.) are offline, these events have to be preserved until the users go online the next time. Then the server has to deliver all missed events.

Most distributed object frameworks provide a naming service, which allows binding/looking up remote references to/by simple names. By this the coupling between clients (looking up) and servers (binding) can be reduced, and the real location of the server object becomes transparent. RMI provides the java.rmi.registry.Registry service (which itself is a Java RMI Remote interface) to accomplish these features. Therefore you’ll have to bind your remote server object to the registry, and look it up in your client.

Finally, the state of the server must not be lost when shutting down normally, so the information must be written to the file system.

Server

Arguments

Your server program does not need any command line arguments this time. Instead a properties file (named messenger.properties) should be read in from the classpath (see the hint section for details). The properties file is provided and can be downloaded here. It contains the port where a registry service has to be started, as well as the name that should be used for binding the server object. Finally the file name in which to store the data persistently is provided.

Messenger remote interface

In RMI you always have to define remote interfaces (interfaces extending java.rmi.Remote). Each method of this interface must throw java.rmi.RemoteException, so errors occurring during the remote invocation can be signaled to the caller. Define your server interface according to the description of the client interactive commands.

Implementation details

On startup you first have to read in the properties file. If the storage file with the given file name exists, you have to read it and restore the server state. See the hint section for help on how to do this. Then create a registry service, export your server object to make it remotely available and finally bind it to the specified name in the registry (help can be found here).

As in Lab1 the server has to manage state across several threads. However, this time you don’t have to create the threads yourself. Instead the RMI runtime takes over this job and transparently uses threads from a thread pool to allow concurrent access to your remote object. This again makes synchronization necessary when accessing your shared data structures!

Note that users may go offline without informing the server appropriately (for example if the computer crashes or the connection is lost). You don’t have to detect this at once, but the next time the server is trying to contact the user (for example to deliver a message): if a java.rmi.RemoteException occurs you may assume the user is offline. Therefore update the user’s state, store the event for the user and inform his/her friends about it.

Your server must also be able to cope with invalid requests, therefore check that each parameter of each method is not null, and otherwise throw a java.lang.IllegalArgumentException. As you will later see the most operations require a logged in user, thus you have to make sure that the caller is really logged in and is whom he/she’s pretending to be (either by using a session identifier or by letting the client submit its user name and password for each operation). Also take care in your server implementation to deal with semantically invalid requests (such as cancelling a non existing friendship, accepting a non existing friendship request, unknown login credentials etc).

The server is responsible for firing notifications if certain events occur (you find the concrete list of events here). If some recipients are not online the server must queue their events (except for "Friend went online/offline" events), and finally inform these users when they login the next time.

If your server is ready for handling requests print “Server up. Hit enter to exit.” to the console and implement this behavior. On exit unbind the remote object from the registry and also unexport it, otherwise your program might not shut down – again you must not use System.exit(). Do not forget to store the server’s state into the specified file. If this file does not exist yet, you have to create it (see hints for a little help).

Client

In this part you have to build an interactive command line client application.

Arguments

Again no arguments are specified on the command line; the client has to read in the same properties file as the server (messenger.properties) from the classpath. You will need the host name and port where the registry service is listening (started by the server) and the binding name of the server object.

Callback messenger remote interface

The server quite often needs to notify the clients about certain events. The server interface you are going to specify only allows clients to contact the server synchronously, but how about notifying the clients about asynchronous events such as a sent message? Polling would be an option, but a very inconvenient one. So the way to go is to define a remote interface for clients, of which the server itself can call methods remotely.

Therefore you have to provide one or several methods that can be called by the server to inform the client about the different events. To sum it up, these events may occur:

  • Friendship requested/accepted/rejected/cancelled
  • Message from friend
  • Friend went offline/online
  • Friend unregistered

If any of these events occurs you have to print an appropriate message containing all the relevant information (including the time of occurrence) to the console.

Implementation details

On startup of your program you first have to read in the properties file. Then you have to obtain a reference to the registry service and look up the server object with the specified name. Note that you can only cast the looked up java.rmi.Remote instance to the remote interface, not the implementing class, because only a stub, which is responsible for communicating with the real server object, is returned!

Now that we’ve got a reference to our server, the user may enter commands (see below). However, note that you have to implement the callback remote interface and export it like you exported your server object. The only difference is that you should not bind it to the registry, but instead simple pass it as parameter for the login command. Since there are quite a lot of different commands, we recommend using the Command pattern, which is described fairly well in this article.

Interactive commands

The following commands must be supported by your client application. Take care of handling invalid commands and arguments and provide usage messages in these cases. Print meaningful error messages whenever the server throws an “expected” exception (such as “Username already exists.” when trying to register with an existing username). Also print success messages if an operation could be executed successfully.
For all commands you first need to login (except for register, login and stop).

  • register <userName> <password> <firstName> <lastName> <birthday in format dd.MM.yyyy> <profession> <emailAddress> {hobby}
    Registers the user at the server. {hobby} describes an optional list of hobbies, where each hobby is exactly one word. You may assume syntactically correct email addresses. However, the server has to check that no other user with the provided username yet exists.
    E.g.:
    :> register user1 password1 FirstName1 LastName1 01.01.1985 student user1@user1.at jogging swimming
    Registration was successful.
  • unregister
    Unregisters the currently logged in user from the server. The server has to take care of informing all friends about the unregistration. On the client side the user must also be logged out afterwards (and the remote callback object unexported).
  • login <username> <password>
    Logs the user in. In your client implementation this is the time to create your callback interface implementation object, export it and pass it as parameter to the server. The server has to return the user’s data (name, birthday, profession, email address and hobbies), his/her direct friends’ public data, pending friendship requests (outgoing as well as incoming friendship requests that have not been accepted or rejected yet) and finally all missed events (see here). Any friend must be informed by the server that the user has gone online now. The output should include the same information as displayed below:
    :> login user1 password1
    Your data:
          Name: FirstName1 LastName1
          Birthday: 01.01.1985
          Profession: student
          Email: user1@user1.at
          Hobbies: 
                  jogging
                  swimming 
          Friends: 
                  user5 online!
                  user6 offline (last online: 05.10.2008 @ 19:06)
          Outgoing friendships requests:
                  user3 since 08.10.2008 @ 13:45         
          Incoming friendships requests:
                  user4 since 08.10.2008 @ 13:41
Events since last logout:
          08.10.2008 @ 13:41:10 | user4 requested friendship. 
          08.10.2008 @ 13:44:41 | user5: Contact me asap! 
  • logout
    Logs out the currently logged in user. Any friend must be informed by the server that the user has gone offline. In your client implementation you have to unexport your previously exported callback remote object.
  • findUser [searchString]
    Queries the server for finding all users matching the search string. Matching in this context means that the search string has to be case-insensitively part of the user name, first name, last name, profession, email address or any hobby. If searchString is omitted, all registered users must be found. The server then has to return the public data of all found users. Your output should include the same information as displayed below:
    :> findUser user
    user1:
          Name: FirstName1 FirstName2
          Birthday: 01.01.1985
          Profession: student
          Email: user1@user1.at
          Hobbies: 
                  jogging
                  swimming 
          Friends: 
                  user5
                  user6
          State: offline, last online on: 08.10.2008 @ 09:00
  • getUserInfo <userName>
    Gets the information of a specific user. The server has to return the user’s public data and the output should contain the same data as above.
  • requestFriend <userName>
    Requests the friendship of another user. The server has to check they are not friends yet and that the other user has not requested friendship yet. Finally let the server inform the recipient of this request about this event.
  • acceptFriend <userName>
    Accepts the requested friendship; the server has to inform the requestor appropriately. In your server you have to check that such a request really exists.
  • rejectFriend <userName>
    Rejects the requested friendship; the server has to inform the requestor appropriately. In your server you have to check that such a request really exists.
  • cancelFriend <userName>
    Cancels an existing friendship with the specified user. The server has to notify the other user about this event.
  • sendMsg <userName> “<message>”
    Sends a message to the specified friend. The message must start and end with a quote – all characters in between (without the quotes) have to be transmitted to the server. The server has to check that the recipient is really a friend and then forwards the message to it.
  • stop
    Stops the client application. If a user is currently logged in you have to log it out implicitly. Again you may not use System.exit(), but have to orderly close all acquired resources.

Lab port policy

Since each student has to start its own registry service (which requires an unused port for listening for requests), we have to make sure that each student uses its own port (this has to be adjusted in messenger.properties).
So if you are testing your solution in the lab environment (i.e. on the lab servers) you have to obey the following rule: you may only use the port 10.000 + dslabXXX * 10 for the registry. So if your account is dslab250 you have to use the port 12500.
As you might have thought of your remote objects also require an unused port. But since we are using the registry for mediation purposes, this can be an anonymous (any available port selected by the operating system) port (see exporting objects for more details).

Ant template

As in Lab1 we provide a template build file (build.xml) in which you only have to adjust some class names. Put your source into the subdirectory "src", place the messenger.properties file into the "src" directory (the ant compile task then copies this file to the build directory) move on the command line to the directory where the build file is located and simply type "ant" for compilation. Type "ant run-server" to start the server, "ant run-client" to start a client.
Put the src directory including messenger.properties and build.xml into your submission.
Note that it's absolutely required that we are able to start your programs with these predefined commands!


Hints & Tricky Parts

  • To make your object remotely available you have to export it. This can either be accomplished by extending java.rmi.server.UnicastRemoteObject or by directly exporting it using the static method java.rmi.server.UnicastRemoteObject.exportObject(Remote obj, int port). Use 0 as port, so any available port is selected by the operating system.
  • Since Java 5 it's not required anymore to create the stubs using the RMI Compiler (rmic). Instead java provides an automatic proxy generation facility when exporting the object.
  • Take care of parameters and return values in your remote interfaces. In RMI all parameters and return values except for remote objects are passed per value. This means that the object is transmitted to the other side using the java serialization mechanism. So it's required that all parameter and return values are serializable, primitives or remote objects, otherwise you will experience java.rmi.UnmarshalExceptions.
  • To create a registry use the static method java.rmi.registry.LocateRegistry.createRegistry(int port). For obtaining a reference in the client you can use the static method java.rmi.registry.LocateRegistry.getRegistry(String hostName, int port). Both hostname and port have to be read from the messenger.properties file.
  • Reading in a properties file from the classpath (without exception handling):
    java.io.InputStream is = ClassLoader.getSystemResourceAsStream("messenger.properties");
    if (is != null) {
    java.util.Properties props = new java.util.Properties();
    try {
    props.load(is);
    String registryHost = props.getProperty("registry.host");
    ...
    } finally {
    is.close();
    }
    } else {
    System.err.println("Properties file not found!");
    }
  • The easiest way for storing/loading your server state to/from the file system is by using serialization. This tutorial shows all the steps you need to accomplish this feature (i.e. making your object serializable by implementing java.io.Serializable, creating a java.io.FileInput/OutputStream, wrapping it using an java.io.ObjectInput/OutputStream and finally reading/writing objects).
  • To create a file in a non existing directory, you first have to create the parent directories. Therefore check whether the file's parent file (= the directory) (java.io.File.getParentFile()) exists, and if not, call java.io.File.mkdirs() on it. Afterwards you can simply use a java.io.FileOutputStream to write to that file.
  • Formatting dates as well as parsing date strings can be easily accomplished by using java.text.SimpleDateFormat. See the API description for more details.
    String dateString = "10.10.2010";
    java.text.DateFormat df = new java.text.SimpleDateFormat("dd.MM.yyyy");
    java.util.Date date = df.parse(dateString);
    String formattedDate = df.format(date);

Further Reading Suggestions

Edit - History - Print - Recent Changes - Search
Page last modified on November 07, 2008, at 10:06 AM CET